home *** CD-ROM | disk | FTP | other *** search
/ PCMania 73 / PCMania CD73_1.iso / sharewar / utiles / viff / viff.c < prev    next >
C/C++ Source or Header  |  1998-08-27  |  55KB  |  2,076 lines

  1. /************************* -*- Mode: C -*- *****************************
  2.  *
  3.  * viff.c -- visual diff
  4.  *
  5.  * Copyright (C) 1994-1998 Richard Flamsholt S0rensen
  6.  *
  7.  * Author          : Richard Flamsholt S0rensen
  8.  * Created On      : Thu Jan 06 21:20:24 1994
  9.  * Last Modified By: Richard Flamsholt S0rensen
  10.  * Last Modified On: Thu Aug 27 13:40:22 1998
  11.  * Update Count    : 1259
  12.  * Revision History: None
  13.  *
  14.  * TODO
  15.  *   goto-local in viffmode shall pick current if that's "selected"
  16.  *   better sideways scroll
  17.  *   viff-highlighting (eg red background) of differences in curr diff
  18.  *   make browse-list for backwards/forwards
  19.  *   unix: fork+freopen+exec, so eg diff process can be interrupted
  20.  *   goto next/prev modification
  21.  *   pg up/down: increment scrolllines, starting with just a few lines
  22.  *   implement more emacs commands (mark region, kill/yank region)
  23.  **********************************************************************/
  24.  
  25. #include <stdio.h>
  26. #include <time.h>
  27. #include <ctype.h>
  28. #include <stdlib.h>
  29. #include <stdarg.h>
  30. #include <string.h>
  31. #include <signal.h>
  32. #include <errno.h>
  33. #include "viff.h"
  34.  
  35. /* requires a compiler with stat() call */
  36. #include <sys/types.h>
  37. #include <sys/stat.h>
  38.  
  39. #ifdef UNIX
  40. #include <fcntl.h>
  41. #include <unistd.h>
  42. #endif
  43.  
  44. #ifdef MSDOS
  45. #include "unixutil.h"
  46. #endif
  47.  
  48. #ifdef BC31
  49. extern unsigned _stklen = 8000;
  50. #endif
  51.  
  52.  
  53. #define WRITE_ALL    0x01
  54. #define EXISTING_FILE    0x02
  55.  
  56. #define DOS_EOL        "\x0d\x0a"
  57. #define UNIX_EOL     "\x0a"
  58.  
  59. #ifdef UNIX
  60. #define VIFFEXT        ".viff"
  61. #define CONTSTR        "..."
  62. #define SPACE_CHAR    '_'
  63. #define TAB_CHAR    '_'
  64. #define CONT_CHAR    '>'
  65. #define CTRL_CHAR    '?'
  66. #define FRAME_TOPCH    'v'
  67. #define FRAME_TOPLEFT    FRAME_TOPCH
  68. #define FRAME_TOPRIGHT    FRAME_TOPCH
  69. #define FRAME_MIDCH    '-'
  70. #define FRAME_MIDLEFT    FRAME_MIDCH
  71. #define FRAME_MIDRIGHT    FRAME_MIDCH
  72. #define FRAME_BOTCH    '^'
  73. #define FRAME_BOTLEFT    FRAME_BOTCH
  74. #define FRAME_BOTRIGHT    FRAME_BOTCH
  75. #define FRAME_NAMELEFT    '('
  76. #define FRAME_NAMERIGHT    ')'
  77. #define ISCTRL(ch)    ((ch)!='\t' && ((unsigned char)((ch)&0x7f)<0x20 || (ch)==0x7f))
  78. #define ADDRAWCH(ch)    (void)addch(ch)
  79. #define EOL        UNIX_EOL
  80. #else
  81. #define VIFFEXT        ".vif"        /* default ext for layout files */
  82. #define CONTSTR        "\xfa\xfa\xfa"    /* if filenames are too long */
  83. #define SPACE_CHAR    0x07        /* small bullet */
  84. #define TAB_CHAR    0x04        /* diamond shape */
  85. #define CONT_CHAR    0x1a        /* small right arrow */
  86. #define CTRL_CHAR    0xf9        /* middle sized dot */
  87. #define FRAME_TOPCH    (unsigned char)196
  88. #define FRAME_TOPLEFT    (unsigned char)218
  89. #define FRAME_TOPRIGHT    (unsigned char)191
  90. #define FRAME_MIDCH    '-'
  91. #define FRAME_MIDLEFT    ' '
  92. #define FRAME_MIDRIGHT    ' '
  93. #define FRAME_BOTCH    (unsigned char)196
  94. #define FRAME_BOTLEFT    (unsigned char)192
  95. #define FRAME_BOTRIGHT    (unsigned char)217
  96. #define FRAME_NAMELEFT    (unsigned char)180
  97. #define FRAME_NAMERIGHT    (unsigned char)195
  98. #define ISCTRL(ch)    ((ch)!='\t' && ((ch)<0x20 || (ch)==0x7f))
  99. #define ADDRAWCH(ch)    (void)addrawch(ch)
  100. #define EOL        DOS_EOL
  101. #endif
  102.  
  103. #define CTRLCH_COLORID    1
  104. #define CTRLCH_FGCOLOR    COLOR_CYAN
  105. #define CTRLCH_BGCOLOR    COLOR_BLACK
  106. #define CTRLCH_ATTR    0    /* becomes A_BOLD when line is selected */
  107. static chtype ctrlch_attr=0;
  108. #define SPACE_COLORID    2
  109. #define SPACE_FGCOLOR    COLOR_CYAN
  110. #define SPACE_BGCOLOR    COLOR_BLACK
  111. #define SPACE_ATTR    0    /* becomes A_BOLD when line is selected */
  112. static chtype space_attr=0;
  113. #define CONT_COLORID    3
  114. #define CONT_FGCOLOR    COLOR_MAGENTA
  115. #define CONT_BGCOLOR    COLOR_BLACK
  116. #define CONT_ATTR    0    /* becomes A_BOLD when line is selected */
  117. static chtype cont_attr=0;
  118. #define SYSL_COLORID 4
  119. #define SYSL_FGCOLOR    COLOR_CYAN
  120. #define SYSL_BGCOLOR    COLOR_BLACK
  121. #define SYSL_ATTR    0
  122. static chtype sysl_attr=0;
  123. #define HELP_COLORID    5
  124. #define HELP_FGCOLOR    COLOR_YELLOW
  125. #define HELP_BGCOLOR    COLOR_BLUE
  126. #define HELP_ATTR    A_BOLD
  127. static chtype help_attr=0;
  128. #define INFO_COLORID    6
  129. #define INFO_FGCOLOR    COLOR_YELLOW
  130. #define INFO_BGCOLOR    COLOR_RED
  131. #define INFO_ATTR    A_BOLD
  132. static chtype info_attr=0;
  133. #define FILE_COLORID    7
  134. #define FILE_FGCOLOR    COLOR_WHITE
  135. #define FILE_BGCOLOR    COLOR_RED
  136. #define FILE_ATTR    A_BOLD
  137. static chtype file_attr=0;
  138. #define VIFF_COLORID    8
  139. #define VIFF_FGCOLOR    COLOR_WHITE
  140. #define VIFF_BGCOLOR    COLOR_GREEN
  141. #define VIFF_ATTR    A_BOLD
  142. chtype viff_attr=0;
  143. #define EDIT_COLORID    9
  144. #define EDIT_FGCOLOR    COLOR_WHITE
  145. #define EDIT_BGCOLOR    COLOR_RED
  146. #define EDIT_ATTR    A_BOLD
  147. chtype edit_attr=0;
  148. chtype status_attr;
  149.  
  150. #define MAXLINELEN    2000
  151. #define LINETABINC    1000
  152. #define VIFFNAME_LEN    30
  153. #define MAXFRAMEWIDTH    240
  154. #define ATTR_COMMON    A_BOLD
  155. #define ATTR_INCL    A_BOLD
  156. #define ATTR_EXCL    0
  157. #define MAXTABWIDTH    32
  158.  
  159. static char *viffmode_help[] = {
  160.   "                       Viff "VERSION,
  161.   "                  by Richard Flamsholt",
  162.   "",
  163.   "previous diff:   C-p up     select file 1 text: C-b left",
  164.   "next diff:       C-n down   select file 2 text: C-f right",
  165.   "first diff:      C-a home   toggle selected:    space",
  166.   "last diff:       C-e end    select all of 1:    (",
  167.   "move in 1/2/all: tab        select all of 2:    )",
  168.   "                            collapse the diff:  +",
  169.   "",
  170.   "move view up:    M-v pgup   save to file 1:     1",
  171.   "move view down:  C-v pgdn   save to file 2:     2",
  172.   "focus on diff:   .          save to both files: 3",
  173.   "goto local diff  C-l        save to new file:   4",
  174.   "search forward:  C-s        save with layout:   5",
  175.   "search backward: C-r",
  176.   "edit text:       C-o ins    goto previous file: <",
  177.   "                            goto next file:     >",
  178.   "set tabwidth:    t          show file info:     i",
  179. #ifdef UNIX
  180.   "redraw screen:   r",
  181. #else
  182.   "",
  183. #endif
  184.   "                            quit: q esc (Q will quit all)",
  185.   ""
  186. };
  187. static char *quitmodified_help[] = {
  188.   "The text has been modified and the",
  189.   "changed files have not been saved.",
  190.   "",
  191.   " yes, quit without saving:       y",
  192.   " no, do not quit:                n esc",
  193.   "",
  194.   " save into file1 and quit:       1",
  195.   " save into file2 and quit:       2",
  196.   " save into both files and quit:  3",
  197.   " save into new file and quit:    4",
  198.   " save layout into file and quit: 5",
  199. };
  200. static char *saveoverwrite_help[] = {
  201.   "A file by that name already exist.",
  202.   "Do you wish to overwrite that file?",
  203.   "",
  204.   " yes, overwrite the file: y",
  205.   " no, cancel:              n"
  206. };
  207. static char *stop_help[] = {
  208.   "You have asked viff to quit.",
  209.   "Do you really want to quit?",
  210.   "",
  211.   " yes, quit viff: y",
  212.   " no, continue:   n"
  213. };
  214.  
  215. static char *header_table[] = {
  216.   "$Header:",
  217.   "$RCSfile:",
  218.   NULL
  219. };
  220.  
  221. typedef enum {
  222.   FOCUS_FILE1, FOCUS_FILE2, FOCUS_BOTH
  223. } FOCUS;
  224. static FOCUS Focus = FOCUS_BOTH;
  225.  
  226. typedef struct {
  227.   int beg1, end1;
  228.   int beg2, end2;
  229.   char cmd;
  230. } EDITCMD;
  231.  
  232. int topln;
  233. int diffbeg, diffmid, diffend;
  234. LINE *linetbl;
  235. int nline = 0;
  236. int linetblsiz = 0;
  237. int tabwidth = 8;
  238. BOOLEAN modified = FALSE;
  239. int Scrollx = 0;
  240.  
  241. BOOLEAN curses_on = FALSE;
  242.  
  243. char *diffcmd = NULL;
  244. char *options = NULL;
  245. BOOLEAN verbose = FALSE;
  246. BOOLEAN report = FALSE;
  247. BOOLEAN brief_report = FALSE;
  248. BOOLEAN ignoreheaders = FALSE;
  249. BOOLEAN monochrome = FALSE;
  250.  
  251. static int ndiff, currdiff, sel1;
  252. static char *Fname1, *Fname2;
  253. static char *Eol1, *Eol2;
  254. static int Nfiles, Fileno;
  255. static int Ndiff = 0;
  256. static int Ndiffrep = 0;
  257.  
  258. static char toptext[MAXFRAMEWIDTH+1];
  259. static char midtext[MAXFRAMEWIDTH+1];
  260. static char bottext[MAXFRAMEWIDTH+1];
  261. static char viffname[VIFFNAME_LEN+1];
  262.  
  263. static BOOLEAN input_filename(char *buf, int ch);
  264. static void version_info(void);
  265. static void file_info(void);
  266. static void file_info_do(WINDOW *win, struct stat *st, int y, int width, char *title, char *fname);
  267. static void ffile_info(FILE *fp);
  268. static void ffile_info_do(FILE *fp, const char *title, char *fname);
  269. static chtype init_color_do(short id, short fg, short bg, chtype attr);
  270. static void init_curses(void);
  271. static void cleanup(void);
  272. static void cleanup_curses(void);
  273. static void free_difflines(void);
  274. static void viff_statusline(void);
  275. static void fstatusline(char *mode, char *fmt, ...);
  276. static void build_text(char *buf, char left, char ch, char right, char *fname);
  277. static void build_frame_texts(void);
  278. static void skip_lines(FILE *fp, int n);
  279. static void buffer_text_line(LINETYPE type, BOOLEAN incl, char *text, int line1, int line2);
  280. static LINE *buffer_line(LINETYPE type, BOOLEAN incl);
  281. static void putline_txt(LINE *line);
  282. static char *read_num(char *str, int *val);
  283. static BOOLEAN read_edit_cmd(FILE *fp, EDITCMD *cmd, BOOLEAN *errmsg);
  284. static int user_commands(void);
  285.  
  286. static BOOLEAN viff_files(int *chp);
  287. static int first_diff_to_show(void);
  288. static BOOLEAN is_header_line(LINE *line, char *header);
  289. static FILE *open_viff_file(char *fname, char **eol, int *chp);
  290. static void collapse_diff(void);
  291. static void choose_diff(BOOLEAN diff1);
  292. static void choose_all_diff(BOOLEAN diff1);
  293. static BOOLEAN write_file(char *fname, char *eol, int flags);
  294. static void goto_diff(int diff);
  295. static void set_curr_diff(int midln, int diff);
  296. static void goto_first_diff(void);
  297. static void goto_last_diff(void);
  298. static BOOLEAN goto_next_diff();
  299. static BOOLEAN goto_prev_diff();
  300. static void set_tab_width();
  301. static void set_focus();
  302. static void check_for_stop(void);
  303.  
  304.  
  305. BOOLEAN
  306. confirm_quit(void)
  307. {
  308.   int ch;
  309.  
  310.   if (!modified && sel1 == ndiff) return TRUE;
  311.   ch = ask(quitmodified_help, ARRAYSIZE(quitmodified_help),
  312.        "yn12345", 'n', "%s%s%s -- really cancel?",
  313.        sel1<ndiff ? "selections made" : "",
  314.        sel1<ndiff && modified ? " and " : "",
  315.        modified ? "text modified" : "");
  316.   switch (ch) {
  317.   case 'y': return TRUE;
  318.   case 'n': return FALSE;
  319.   case '1': return write_file(Fname1, Eol1, EXISTING_FILE);
  320.   case '2': return write_file(Fname2, Eol2, EXISTING_FILE);
  321.   case '3': return (write_file(Fname1, Eol1, EXISTING_FILE) &&
  322.             write_file(Fname2, Eol2, EXISTING_FILE));
  323.   case '4':
  324.   case '5':
  325.     { char buf[300];
  326.       if (!input_filename(buf, ch)) return FALSE;
  327.       return write_file(buf, EOL, ch=='5'?WRITE_ALL:0);
  328.     }
  329.   }
  330.   /* we should never come here */
  331.   return FALSE;
  332. }
  333.  
  334.  
  335. static BOOLEAN
  336. input_filename(char *buf, int ch)
  337. {
  338.   struct stat st;
  339.   char *save;
  340.   char *p;
  341.  
  342.   strcpy(buf, Fname1);
  343.   p = strip_path(buf);
  344.   if (ch == '4') {            /* save text; suggest Fname1's path */
  345.     save = "text";
  346.     *p = '\0';
  347.   } else {                /* save layout; suggest Fname1.vif */
  348.     save = "layout";
  349. #ifdef MSDOS
  350.     p = strchr(p, '.');            /* find the extension, if any */
  351.     if (p) strcpy(p, VIFFEXT);
  352.     else
  353. #endif
  354.       strcat(buf, VIFFEXT);
  355.   }
  356.   return (input(0, buf, "enter filename (saving %s):", save) &&
  357.       (stat(buf, &st) != 0 ||
  358.        ask(saveoverwrite_help, ARRAYSIZE(saveoverwrite_help),
  359.            "yn", 'n', "file \"%s\" exist - overwrite it?", buf) == 'y'));
  360. }
  361.  
  362.  
  363. void
  364. statusline(unsigned flags, const char *fmt, ...)
  365. {
  366.   va_list varg;
  367.  
  368.   va_start(varg, fmt);
  369.   vstatusline(flags, fmt, varg);
  370.   va_end(varg);
  371. }
  372.  
  373.  
  374. int
  375. vstatusline(unsigned flags, const char *fmt, va_list varg)
  376. {
  377.   char buf[300];
  378.   int len, i;
  379.  
  380.   vsprintf(buf, fmt, varg);
  381.   (void)attron(status_attr);
  382.   (void)move(VIEWLINES, 0);
  383.   (void)clrtoeol();
  384.   (void)addch(' ');
  385.   (void)addstr(buf);
  386.   len = strlen(buf)+1;
  387.   for (i = len; i < COLS; i++) {
  388.     (void)addch(' ');
  389.   }
  390.  
  391.   buf[0] = '\0';
  392.   if (flags & STATUS_RATING && ndiff >= 300) {
  393.     char *rating;
  394.     if      (ndiff <   400) rating = "hard";
  395.     else if (ndiff <   600) rating = "tough";
  396.     else if (ndiff ==  666) rating = "beast";
  397.     else if (ndiff <   800) rating = "nasty";
  398.     else if (ndiff <  1000) rating = "monster";
  399.     else if (ndiff == 1001) rating = "svedig";
  400.     else if (ndiff <  1500) rating = "killer";
  401.     else if (ndiff <  2000) rating = "evil";
  402.     else if (ndiff <  2500) rating = "deathly";
  403.     else                    rating = "argh!";
  404.     sprintf(buf, "(%s) ", rating);
  405.   }
  406.   if (flags & STATUS_HELP) {
  407.     strcat(buf, "F1=help");
  408.   }
  409.   if (buf[0] != '\0') {
  410.     (void)move(VIEWLINES, COLS-1 - strlen(buf));
  411.     (void)addstr(buf);
  412.   }
  413.  
  414.   (void)standend();
  415.  
  416.   return len;
  417. }
  418.  
  419.  
  420. static void
  421. fstatusline(char *mode, char *fmt, ...)
  422. {
  423.   char status[100];
  424.   va_list varg;
  425.  
  426.   va_start(varg, fmt);
  427.   vsprintf(status, fmt, varg);
  428.   va_end(varg);
  429.   statusline(STATUS_HELP|STATUS_RATING,
  430.          " %s %s %s  %s",
  431.          mode, modified ? "**" : "  ",
  432.          viffname, status);
  433. }
  434.  
  435.  
  436. static void
  437. version_info(void)
  438. {
  439.   printf("Viff " VERSION "\n");
  440.   printf("Copyright 1994-1998 Richard Flamsholt\n");
  441.   printf("Compiled %s (%s %s)\n", viffdate, PLATFORM, COMPILER);
  442. }
  443.  
  444.  
  445. #define FRAME           1
  446. #define XINDENT         1
  447. #define XEXTRA          (FRAME+XINDENT+XINDENT+FRAME)
  448. #define YEXTRA          (FRAME+FRAME)
  449.  
  450. void
  451. help(char *text[], int nlines)
  452. {
  453.   WINDOW *win;
  454.   int width;
  455.   int i;
  456.  
  457.   for (i=0, width=0; i < nlines; i++) {
  458.     int len = strlen(text[i]);
  459.     if (width < len) width = len;
  460.   }
  461.  
  462.   /* handle too small screens */
  463.   if (nlines > LINES-XEXTRA) nlines = LINES-XEXTRA;
  464.   if (width > COLS-YEXTRA) width = COLS-YEXTRA;
  465.  
  466.   /* newwin(height,width,top,left) */
  467.   win = newwin(nlines+YEXTRA,
  468.            width+XEXTRA,
  469.            (LINES-(nlines+YEXTRA))/3,  /* place 1/3 from top of screen */
  470.            (COLS-(width+XEXTRA))/2);   /* place in the middle of screen */
  471.   if (win == (WINDOW*)NULL) {
  472.     message("could not create help window");
  473.     return;
  474.   }
  475.   (void)wattron(win, help_attr);
  476.   (void)wbkgd(win, COLOR_PAIR(HELP_COLORID));
  477.   (void)werase(win);
  478.   (void)box(win, 0, 0);
  479.   for (i = 0; i < nlines; i++) {
  480.     (void)mvwaddstr(win, i+FRAME, FRAME+XINDENT, text[i]);
  481.   }
  482.   (void)wmove(win, 0, 0);
  483.   (void)wrefresh(win);
  484.   (void)getch();
  485.   (void)delwin(win);
  486.   (void)touchwin(stdscr);
  487.   (void)refresh();
  488. }
  489.  
  490.  
  491. static void
  492. file_info(void)
  493. {
  494.   WINDOW *win;
  495.   char buf[100];
  496.   char title[50];
  497.   struct stat st1, st2;
  498.   int width, height;
  499.   int ypos;
  500.   long dt;
  501.  
  502.   strcpy(title, "Info");
  503.   if (Nfiles > 1) {
  504.     sprintf(title+strlen(title), " (%d of %d)", Fileno, Nfiles);
  505.   }
  506.   width = MAX(strlen(Fname1), strlen(Fname2)) + 1 +2+6+2;
  507.   width = MAX((int)strlen(title) + 2, width);
  508.   width = MAX(20+1 + 2+6+2, MIN(width, COLS-4));
  509.   height = 16;
  510.   win = newwin(height, width, (LINES-height)/3, (COLS-width)/2);
  511.   if (win == (WINDOW*)NULL) {
  512.     message("could not create info window");
  513.     return;
  514.   }
  515.   (void)wattron(win, info_attr);
  516.   (void)wbkgd(win, COLOR_PAIR(INFO_COLORID));
  517.   (void)werase(win);
  518.   (void)box(win, 0, 0);
  519.   ypos = 1;
  520.   (void)mvwaddstr(win, ypos++, (width-(int)strlen(title))/2, title);
  521.   ypos++;
  522.   file_info_do(win, &st1, ypos,   width, "File 1", Fname1);
  523.   file_info_do(win, &st2, ypos+7, width, "File 2", Fname2);
  524.  
  525.   (void)wattroff(win, info_attr); (void)wattron(win, file_attr);
  526.   if (st1.st_size == st2.st_size) {
  527.     strcpy(buf, "same size");
  528.   } else {
  529.     long dsize = labs(st1.st_size - st2.st_size);
  530.     sprintf(buf, "%ld byte%s %s",
  531.         dsize, dsize==1?"":"s",
  532.         st1.st_size < st2.st_size ? "smaller" : "bigger");
  533.   }
  534.   (void)mvwaddstr(win, ypos+4, 3, buf);
  535.   dt = labs(st1.st_mtime - st2.st_mtime);
  536.   if (dt == 0) {
  537.     strcpy(buf, "same age");
  538.   } else {
  539.     if (dt < 60) {            /* less than a minute */
  540.       sprintf(buf, "%ld second%s", dt, dt==1?"":"s");
  541.     } else if ((dt/=60) < 60) {        /* less than an hour */
  542.       sprintf(buf, "%ld minute%s", dt, dt==1?"":"s");
  543.     } else if ((dt/=60) < 24) {        /* less than a day */
  544.       sprintf(buf, "%ld hour%s", dt, dt==1?"":"s");
  545.     } else if ((dt/=24) < 2*30) {    /* less than two months */
  546.       sprintf(buf, "%ld day%s", dt, dt==1?"":"s");
  547.     } else if (dt < 366) {        /* less than a year */
  548.       dt /= 30;                /* 30 is approx one month */
  549.       sprintf(buf, "%ld month%s", dt, dt==1?"":"s");
  550.     } else {                /* more than a year */
  551.       dt /= 366;
  552.       sprintf(buf, "%ld year%s", dt, dt==1?"":"s");
  553.     }
  554.     strcat(buf, st1.st_mtime < st2.st_mtime ? " older" : " newer");
  555.   }
  556.   (void)mvwaddstr(win, ypos+5, 3, buf);
  557.   (void)wattroff(win, file_attr); (void)wattron(win, info_attr);
  558.  
  559.   (void)wmove(win, 0, 0);
  560.   (void)wrefresh(win);
  561.   (void)getch();
  562.   (void)delwin(win);
  563.   (void)touchwin(stdscr);
  564.   (void)refresh();
  565. }
  566.  
  567.  
  568. static void
  569. file_info_do(WINDOW *win, struct stat *st, int y, int width, char *title, char *fname)
  570. {
  571.   char cont[10];
  572.   int xpos;
  573.  
  574.   xpos = 2;
  575.   (void)mvwaddstr(win, y+0, xpos, title);
  576.   (void)mvwaddstr(win, y+1, xpos, "Name:");
  577.   (void)mvwaddstr(win, y+2, xpos, "Date:");
  578.   (void)mvwaddstr(win, y+3, xpos, "Size:");
  579.   xpos += 6;
  580.  
  581.   (void)wattroff(win, info_attr); (void)wattron(win, file_attr);
  582.   if (stat(fname, st) != 0) {
  583.     (void)mvwaddstr(win, y+2, xpos, "?");
  584.     (void)mvwaddstr(win, y+3, xpos, "?");
  585.   } else {
  586.     char buf[100];
  587.     strftime(buf, sizeof buf, "%a %b %d %H:%M %Y",
  588.          localtime((time_t*)&st->st_mtime));
  589.     (void)mvwaddstr(win, y+2, xpos, buf);
  590.     sprintf(buf, "%ld byte%s", (long)st->st_size, st->st_size==1?"":"s");
  591.     (void)mvwaddstr(win, y+3, xpos, buf);
  592.   }
  593.   width -= 1+2+6+2;
  594.   cont[0] = '\0';
  595.   if ((int)strlen(fname) > width) {
  596. #if defined(MSDOS) || defined(WIN32)
  597.     if (isalpha(((unsigned char*)fname)[0]) && fname[1] == ':') {
  598.       memcpy(cont, fname, fname[2]==SEPCHAR ? 3 : 2);
  599.     }
  600. #endif
  601.     strcat(cont, CONTSTR);
  602.     fname += strlen(cont)+strlen(fname)-width;
  603.   }
  604.   (void)mvwaddstr(win, y+1, xpos, cont);
  605.   (void)mvwaddstr(win, y+1, xpos+strlen(cont), fname);
  606.   (void)wattroff(win, file_attr); (void)wattron(win, info_attr);
  607. }
  608.  
  609.  
  610. static void
  611. ffile_info(FILE *fp)
  612. {
  613.   time_t t = time((time_t*)NULL);
  614.   char *tim = ctime(&t);
  615.  
  616.   tim[strlen(tim)-1] = '\0';        /* remove that stupid trailing \n */
  617.   fprintf(fp, EOL "%*sViff Report" EOL, 34, "");
  618.   fprintf(fp, EOL "%*s%s" EOL EOL, 27, "", tim);
  619.   ffile_info_do(fp, "File 1", Fname1);
  620.   ffile_info_do(fp, "File 2", Fname2);
  621.   fprintf(fp, EOL "%d difference%s" EOL, ndiff, ndiff==1?"":"s");
  622.   fprintf(fp, EOL "[***end of viff header***]" EOL);
  623. }
  624.  
  625.  
  626. static void
  627. ffile_info_do(FILE *fp, const char *title, char *fname)
  628. {
  629.   struct stat st;
  630.  
  631.   fprintf(fp, "%s:" EOL "   Name: %s" EOL, title, fname);
  632.   if (stat(fname, &st) == 0) {
  633.     char buf[100];
  634.     strftime(buf, sizeof buf, "%a %d %b %H:%M",
  635.          localtime((time_t*)&st.st_mtime));
  636.     fprintf(fp, "   Date: %s" EOL, buf);
  637.     fprintf(fp, "   Size: %ld byte%s" EOL, (long)st.st_size, st.st_size==1?"":"s");
  638.   }
  639. }
  640.  
  641.  
  642. static chtype
  643. init_color_do(short id, short fg, short bg, chtype attr)
  644. {
  645.   if (init_pair(id, fg, bg) == ERR) {
  646.     error("failed to initialize color %d", id);
  647.   }
  648.   return attr|COLOR_PAIR(id);
  649. }
  650.  
  651.  
  652. static void
  653. init_curses(void)
  654. {
  655.   if (report) return;            /* no init nor cleanup for reports */
  656. #ifdef UNIX
  657.   if (!isatty(1)) {
  658.     close(1);
  659.     if (open("/dev/tty", O_WRONLY) == -1) { /* reopen stdout to terminal */
  660.       error("you cannot pipe viff's output: %s", my_strerror());
  661.     }
  662.   }
  663. #endif
  664.   if (initscr() == (WINDOW*)NULL) {
  665.     panic("curses: initscr() failed");
  666.   }
  667.   atexit(cleanup_curses);
  668.   curses_on = TRUE;
  669.   if (!monochrome && has_colors() && start_color()==OK) {
  670.     ctrlch_attr = init_color_do(CTRLCH_COLORID, CTRLCH_FGCOLOR, CTRLCH_BGCOLOR, CTRLCH_ATTR);
  671.     space_attr = init_color_do(SPACE_COLORID, SPACE_FGCOLOR, SPACE_BGCOLOR, SPACE_ATTR);
  672.     cont_attr = init_color_do(CONT_COLORID, CONT_FGCOLOR, CONT_BGCOLOR, CONT_ATTR);
  673.     sysl_attr = init_color_do(SYSL_COLORID, SYSL_FGCOLOR, SYSL_BGCOLOR, SYSL_ATTR);
  674.     help_attr = init_color_do(HELP_COLORID, HELP_FGCOLOR, HELP_BGCOLOR, HELP_ATTR);
  675.     info_attr = init_color_do(INFO_COLORID, INFO_FGCOLOR, INFO_BGCOLOR, INFO_ATTR);
  676.     file_attr = init_color_do(FILE_COLORID, FILE_FGCOLOR, FILE_BGCOLOR, FILE_ATTR);
  677.     viff_attr = init_color_do(VIFF_COLORID, VIFF_FGCOLOR, VIFF_BGCOLOR, VIFF_ATTR);
  678.     edit_attr = init_color_do(EDIT_COLORID, EDIT_FGCOLOR, EDIT_BGCOLOR, EDIT_ATTR);
  679.   } else {
  680.     viff_attr = A_REVERSE;
  681.     edit_attr = A_REVERSE;
  682.   }
  683.   status_attr = viff_attr;
  684.   (void)raw();
  685.   (void)noecho();
  686.   (void)keypad(stdscr, TRUE);
  687.   if (COLS > MAXFRAMEWIDTH) {
  688.     panic("cannot display %d columns; max is %d", COLS, MAXFRAMEWIDTH);
  689.   }
  690. }
  691.  
  692.  
  693. static void
  694. cleanup(void)
  695. {
  696.   free_difflines();
  697.   free(linetbl);
  698.   free(options);
  699.   free(diffcmd);
  700. }
  701.  
  702.  
  703. static void
  704. cleanup_curses(void)
  705. {
  706.   (void)move(VIEWLINES, 0);
  707.   (void)clrtoeol();
  708.   (void)refresh();
  709.   (void)endwin();
  710.   setbuf(stdout, NULL);
  711.   curses_on = FALSE;
  712. }
  713.  
  714.  
  715. static void
  716. free_difflines(void)
  717. {
  718.   while (0 < nline) {
  719.     nline--;
  720.     if (!SYSLINE(linetbl+nline)) free(linetbl[nline].txt);
  721.   }
  722. }
  723.  
  724.  
  725. static void
  726. viff_statusline(void)
  727. {
  728.   char diffinfo[40];
  729.   char focus[30];
  730.  
  731.   if (diffend-diffbeg < VIEWLINES) {
  732.     diffinfo[0] = '\0';
  733.   } else {
  734.     sprintf(diffinfo, "  (%d+%d)", diffmid-diffbeg-1, diffend-diffmid-1);
  735.   }
  736.   if (Focus == FOCUS_BOTH) {
  737.     focus[0] = '\0';
  738.   } else {
  739.     int n = Focus==FOCUS_FILE1 ? sel1 : ndiff-sel1;
  740.     char dir =Focus==FOCUS_FILE1 ? '<' : '>';
  741.     sprintf(focus, "  %cnavigate %d diff%s%c", dir, n, n==1?"":"s", dir);
  742.   }
  743.  
  744.   fstatusline("Viff", "%d/%d  %d-%d%s%s",
  745.           currdiff, ndiff, sel1, ndiff-sel1, diffinfo, focus);
  746.  
  747.   if (topln <= diffmid && diffmid < topln+VIEWLINES) {
  748.     (void)move(diffmid-topln, 0);
  749.   } else {
  750.     (void)move(VIEWLINES, 0);
  751.   }
  752. }
  753.  
  754.  
  755. void
  756. edit_statusline(LINE *line, int memx, int winx)
  757. {
  758.   char colbuf[20], linebuf[40];
  759.   char chbuf[50];
  760.  
  761.   chbuf[0] = '\0';
  762.   if (SYSLINE(line)) {
  763.     strcpy(colbuf, "- "); strcpy(linebuf, "-");
  764.   } else {
  765.     sprintf(colbuf, "%-2d", winx+1);
  766.     if (line->new) {
  767.       strcpy(linebuf, "new");
  768.     } else {
  769.       char *p = linebuf;
  770.       if (line->broken) *p++ = '*';
  771.       if (line->line1==0) *p++ = '-';
  772.       else p+=sprintf(p, "%u", line->line1);
  773.       if (line->line1 == 0 || line->line1 != line->line2) {
  774.     *p++ = '/';
  775.     if (line->line2==0) *p++ = '-';
  776.     else p+=sprintf(p, "%d", line->line2);
  777.       }
  778.       *p = '\0';
  779.     }
  780.     if (memx < line->len) {
  781.       unsigned char ch = line->txt[memx];
  782.       if (ISCTRL(ch) || ch >= 0x80) {
  783.     sprintf(chbuf, "  char= %#x %u %#o", ch, ch, ch);
  784.     if (ch < 0x20 && isupper(ch+0x40)) {
  785.       sprintf(chbuf+strlen(chbuf), " ^%c", ch+0x40);
  786.     }
  787.       }
  788.     }
  789.   }
  790.   fstatusline("Edit", "col:%s lin:%s%s", colbuf, linebuf, chbuf);
  791. }
  792.  
  793.  
  794. static void
  795. build_text(char *buf, char left, char ch, char right, char *fname)
  796. {
  797.   char cont[10] = {0};
  798.  
  799.   memset(buf, ch, COLS);
  800.   if ((int)strlen(fname) > COLS/2) {
  801. #if defined(MSDOS) || defined(WIN32)
  802.     if (isalpha(((unsigned char*)fname)[0]) && fname[1] == ':') {
  803.       memcpy(cont, fname, fname[2]==SEPCHAR ? 3 : 2);
  804.     }
  805. #endif
  806.     strcat(cont, CONTSTR);
  807.     fname += strlen(cont)+strlen(fname)-COLS/2;
  808.   }
  809.   buf[COLS/4+sprintf(buf+COLS/4, "%c%s%s%c",
  810.              FRAME_NAMELEFT, cont, fname, FRAME_NAMERIGHT)] = ch;
  811.   buf[0] = left;
  812.   buf[COLS-1] = right;
  813. }
  814.  
  815.  
  816. static void
  817. build_frame_texts(void)
  818. {
  819.   memset(midtext, FRAME_MIDCH, COLS);
  820.   midtext[0] = FRAME_MIDLEFT;
  821.   midtext[COLS-1] = FRAME_MIDRIGHT;
  822.   build_text(toptext, FRAME_TOPLEFT, FRAME_TOPCH, FRAME_TOPRIGHT, Fname1);
  823.   build_text(bottext, FRAME_BOTLEFT, FRAME_BOTCH, FRAME_BOTRIGHT, Fname2);
  824. }
  825.  
  826.  
  827. static void
  828. skip_lines(FILE *fp, int n)
  829. {
  830.   while (n-- > 0) {
  831.     while (fgetc(fp) != '\n') {
  832.       if (feof(fp)) break;
  833.     }
  834.   }
  835. }
  836.  
  837.  
  838. static void
  839. buffer_text_line(LINETYPE type, BOOLEAN incl, char *text, int line1, int line2)
  840. {
  841.   LINE *line;
  842.  
  843.   if (report) return;            /* do nothing */
  844.   line = buffer_line(type, incl);
  845.   line->len = strlen(text);
  846.   if (0 < line->len && text[line->len-1] == '\n') {
  847.     line->len--;            /* get rid of trailing '\n' */
  848.   }
  849.   line->room   = 0;
  850.   line->txt    = memcpy(xmalloc(line->len), text, line->len);
  851.   line->line1  = line1;
  852.   line->line2  = line2;
  853.   line->new    = FALSE;
  854.   line->broken = FALSE;
  855. }
  856.  
  857.  
  858. static LINE *
  859. buffer_line(LINETYPE type, BOOLEAN incl)
  860. {
  861.   LINE *line;
  862.  
  863.   if (report) return NULL;
  864.   if (nline == linetblsiz) {            /* enlarge line table */
  865. #ifdef MSDOS
  866. #define MAX_LINES  (unsigned)(0xff80/sizeof(*line))
  867.     if (linetblsiz == MAX_LINES) {    /* cannot enlarge table */
  868.       panic("too many lines in file; max %u", MAX_LINES);
  869.     } else if (linetblsiz+LINETABINC > MAX_LINES) {  /* make it maxsize */
  870.       linetblsiz = MAX_LINES;        /* next enlargement will panic() */
  871.     } else
  872. #endif
  873.       linetblsiz += LINETABINC;           /* and do it in chunks */
  874.     linetbl = xrealloc(linetbl, sizeof(*line)*linetblsiz);
  875.   }
  876.   line = linetbl + nline++;             /* shorthand alias */
  877.   line->type = type;
  878.   line->incl = incl;
  879.   return line;
  880. }
  881.  
  882.  
  883. void
  884. set_topline(int ln)
  885. {
  886.   int beg, end;
  887.  
  888.   if (ln == topln) return;
  889.   (void)move(0, 0);
  890.   if (topln < ln && ln < topln+VIEWLINES) {
  891.     beg = topln+VIEWLINES; end = ln+VIEWLINES;
  892.     do {
  893.       deleteln();
  894.     } while (++topln < ln);
  895.   } else if (topln-VIEWLINES < ln && ln < topln) {
  896.     beg = ln; end = topln;
  897.     do {
  898.       insertln();
  899.     } while (ln < --topln);
  900.   } else {
  901.     topln = ln;
  902.     beg = topln;
  903.     end = topln+VIEWLINES;
  904.   }
  905.   for (ln = beg; ln < end; ln++) {
  906.     putline(ln);
  907.   }
  908.   viff_statusline();
  909. }
  910.  
  911.  
  912. static void
  913. putline_txt(LINE *line)
  914. {
  915.   int memx, winx;
  916.   char *p, *pws, *pend, *pbeg;
  917.   unsigned char space, tab;
  918.  
  919.   pbeg = NULL;
  920.   for (winx=0, memx=0, p=line->txt; memx < line->len; memx++, p++) {
  921.     if (pbeg == NULL && winx >= Scrollx) {
  922.       pbeg = winx>Scrollx ? p-1 : p;    /*  > if there's a tab; go back */
  923.     }
  924.     if (winx >= Scrollx+COLS) break;
  925.     if (*p == '\t') {
  926.       winx += tabwidth-winx%tabwidth;
  927.     } else {
  928.       winx++;
  929.     }
  930.   }
  931.   if (pbeg == NULL) {                   /* no visible text */
  932.     (void)clrtoeol();
  933.     return;
  934.   }
  935.   /* p now points just beyond text to print and pbeg points to its start */
  936.  
  937.   /* make pws point to last non-whitespace character */
  938.   pws = pend = p;
  939.   if (memx == line->len) {
  940.     for (; pbeg < pws; pws--) {
  941.       if (!isspace(((unsigned char*)pws)[-1])) break;
  942.     }
  943.   }
  944.  
  945.   space = tab = ' ';
  946.   for (winx=0, p=pbeg; p < pend; p++) {
  947.     if (winx == COLS-1 && p+1-line->txt < line->len) {
  948.       (void)attron(cont_attr);
  949.       ADDRAWCH(CONT_CHAR); winx++;
  950.       break;
  951.     }
  952.     if (p == pws) {
  953.       space = SPACE_CHAR;
  954.       tab   = TAB_CHAR;
  955.       (void)attron(space_attr);
  956.     }
  957.     switch (*p) {
  958.     case '\t':
  959.       do {
  960.     if (winx == COLS-1 &&
  961.         ((winx+1+Scrollx) % tabwidth || p+1-line->txt < line->len)) {
  962.       (void)attron(cont_attr);
  963.       ADDRAWCH(CONT_CHAR); winx++;
  964.       break;
  965.     }
  966.     ADDRAWCH(tab); winx++;
  967.       } while ((winx+Scrollx) % tabwidth);
  968.       break;
  969.     case ' ':
  970.       ADDRAWCH(space); winx++;
  971.       break;
  972.     default:
  973.       if (ISCTRL(*(unsigned char*)p)) {
  974.     (void)attron(ctrlch_attr);
  975.     ADDRAWCH(CTRL_CHAR);
  976.     (void)attroff(ctrlch_attr);
  977.       } else {
  978.     ADDRAWCH(*(unsigned char*)p);
  979.       }
  980.       winx++;
  981.       break;
  982.     }
  983.     if (winx == COLS) break;
  984.   }
  985.   if (winx < COLS) {
  986.     (void)clrtoeol();
  987.   }
  988. }
  989.  
  990.  
  991. void
  992. putline(int ln)
  993. {
  994.   LINE *line = linetbl + ln;
  995.  
  996.   if (ln < topln || topln+VIEWLINES <= ln) return;
  997.   if (nline <= ln) {
  998.     (void)move(ln-topln, 0);
  999.     (void)clrtoeol();
  1000.   } else if (!SYSLINE(line)) {
  1001.     (void)attron(line->type == COMMON ? ATTR_COMMON :
  1002.          line->incl           ? ATTR_INCL :
  1003.                                         ATTR_EXCL);
  1004.     (void)move(ln-topln, 0);
  1005.     putline_txt(line);
  1006.     (void)standend();
  1007.   } else {
  1008.     char *text, *p;
  1009.     chtype attr = 0;
  1010.     switch (line->type) {
  1011.     case TOPSEP: text = toptext; break;
  1012.     case MIDSEP: text = midtext; break;
  1013.     case BOTSEP: text = bottext; break;
  1014.     }
  1015.     (void)attron(sysl_attr|attr);
  1016.     (void)move(ln-topln, 0);
  1017.     for (p = text; *p; p++) {
  1018.       ADDRAWCH(*(unsigned char*)p);
  1019.     }
  1020.     (void)standend();
  1021.     if (COLS < p-text) {
  1022.       (void)clrtoeol();
  1023.     }
  1024.   }
  1025. }
  1026.  
  1027.  
  1028. void
  1029. goto_curr_diff(BOOLEAN center)
  1030. {
  1031.   int diffsight;
  1032.   int lines;
  1033.   int ln;
  1034.  
  1035.   if (!center &&
  1036.       diffbeg >= topln && diffend < topln+VIEWLINES) return;  /* all visible */
  1037.  
  1038.   /* I want to position the diff the absolute best way possible!
  1039.      If diff can't be viewed entirely then
  1040.        if      both beginning and middle can be viewed, set beginning at top
  1041.        else if both middle and bottom can be viewed, set end at bottom
  1042.        else center middle of diff on the screen
  1043.      If diff can be viewed entirely then
  1044.        if      beginning is visible, scroll so the end is at the bottom
  1045.        else if end is visible, scroll so beginning is at the top
  1046.        else center the entire diff on the screen
  1047.        Also, if possible to show DIFFSIGHT lines to both sides of the diff
  1048.    */
  1049.   lines = diffend - diffbeg + 1;
  1050.   if (lines > VIEWLINES) {
  1051.     if (diffmid-diffmid+1 <= VIEWLINES) {
  1052.       ln = diffbeg;
  1053.     } else if (diffend-diffmid+1 <= VIEWLINES) {
  1054.       ln = diffend - (VIEWLINES-1);
  1055.     } else {
  1056.       ln = diffmid - (int)(VIEWLINES/2);
  1057.     }
  1058.   } else if (center) {
  1059.     ln = diffbeg - (int)((VIEWLINES-lines)/2);
  1060.   } else {
  1061.     diffsight = MIN(DIFFSIGHT, VIEWLINES-lines);
  1062.     if (diffbeg >= topln && diffbeg < topln+VIEWLINES) {
  1063.       ln = diffend+diffsight - (VIEWLINES-1);
  1064.     } else if (diffend >= topln && diffend < topln+VIEWLINES) {
  1065.       ln = diffbeg-diffsight;
  1066.     } else {
  1067.       ln = diffbeg - (int)((VIEWLINES-lines)/2);
  1068.     }
  1069.   }
  1070.   if (nline-VIEWLINES < ln) ln = nline-VIEWLINES;
  1071.   if (ln < 0) ln = 0;
  1072.   set_topline(ln);
  1073.   viff_statusline();
  1074. }
  1075.  
  1076.  
  1077. static void
  1078. goto_diff(int diff)
  1079. {
  1080.   int ln, n, curr;
  1081.  
  1082.   for (n=diff, ln=0, curr=0; ln < nline; ln++) {
  1083.     if (linetbl[ln].type == MIDSEP) {
  1084.       curr++;
  1085.       if (Focus == FOCUS_BOTH  && --n==0) break;
  1086.       if (Focus == FOCUS_FILE1 && linetbl[ln-1].incl && --n==0) break;
  1087.       if (Focus == FOCUS_FILE2 && linetbl[ln+1].incl && --n==0) break;
  1088.     }
  1089.   }
  1090.   if (ln == nline) return;              /* no such diff */
  1091.   set_curr_diff(ln, curr);
  1092. }
  1093.  
  1094.  
  1095. static void
  1096. set_curr_diff(int midln, int diff)
  1097. {
  1098.   diffmid = midln;
  1099.   for (diffend = midln+1; linetbl[diffend].type != BOTSEP; diffend++)
  1100.     ;
  1101.   for (diffbeg = midln-1; linetbl[diffbeg].type != TOPSEP; diffbeg--)
  1102.     ;
  1103.   currdiff = diff;
  1104.   goto_curr_diff(FALSE);
  1105. }
  1106.  
  1107.  
  1108. static void
  1109. goto_first_diff(void)
  1110. {
  1111.   goto_diff(1);
  1112. }
  1113.  
  1114.  
  1115. static void
  1116. goto_last_diff(void)
  1117. {
  1118.   goto_diff(Focus == FOCUS_BOTH  ? ndiff :
  1119.             Focus == FOCUS_FILE1 ? sel1  : ndiff-sel1);
  1120. }
  1121.  
  1122.  
  1123. static BOOLEAN
  1124. goto_next_diff()
  1125. {
  1126.   int ln, curr;
  1127.  
  1128.   for (ln=diffend+1, curr=currdiff; ln < nline; ln++) {
  1129.     if (linetbl[ln].type == MIDSEP) {
  1130.       curr++;
  1131.       if (Focus == FOCUS_BOTH) break;
  1132.       if (Focus == FOCUS_FILE1 && linetbl[ln-1].incl) break;
  1133.       if (Focus == FOCUS_FILE2 && linetbl[ln+1].incl) break;
  1134.     }
  1135.   }
  1136.   if (ln == nline) return FALSE;        /* no more diffs ahead of us */
  1137.   set_curr_diff(ln, curr);
  1138.   return TRUE;
  1139. }
  1140.  
  1141.  
  1142. static BOOLEAN
  1143. goto_prev_diff()
  1144. {
  1145.   int ln, curr;
  1146.  
  1147.   for (ln=diffbeg-1, curr=currdiff; ln >= 0; ln--) {
  1148.     if (linetbl[ln].type == MIDSEP) {
  1149.       curr--;
  1150.       if (Focus == FOCUS_BOTH) break;
  1151.       if (Focus == FOCUS_FILE1 && linetbl[ln-1].incl) break;
  1152.       if (Focus == FOCUS_FILE2 && linetbl[ln+1].incl) break;
  1153.     }
  1154.   }
  1155.   if (ln < 0) return FALSE;             /* no more diffs before this point */
  1156.   set_curr_diff(ln, curr);
  1157.   return TRUE;
  1158. }
  1159.  
  1160.  
  1161. void
  1162. goto_nearest_diff(int ln)
  1163. {
  1164.   int i, prev, next;
  1165.   int diff;
  1166.   LINE *line;
  1167.  
  1168.   switch (linetbl[ln].type) {
  1169.   case DIFF:                /* in diff; look for mid or bottom  */
  1170.     while (linetbl[++ln].type == DIFF)
  1171.       ;
  1172.     break;
  1173.   case COMMON:                /* outside diff; pick nearest */
  1174.     for (prev = ln-1; prev >= 0; prev--)
  1175.       if (linetbl[prev].type == BOTSEP) break;
  1176.     for (next = ln+1; next < nline; next++)
  1177.       if (linetbl[next].type == TOPSEP) break;
  1178.     if (prev < 0) ln = next;
  1179.     else if (next == nline) ln = prev;
  1180.     else ln = ln-prev < next-ln ? prev : next;
  1181.     break;
  1182.   }
  1183.  
  1184.   /* now we're at top, bottom, or middle of a diff */
  1185.   switch (linetbl[ln].type) {
  1186.   case TOPSEP:                /* at top; look for middle */
  1187.     while (linetbl[++ln].type != MIDSEP)
  1188.       ;
  1189.     break;
  1190.   case MIDSEP:                /* already at middle of diff */
  1191.     break;
  1192.   case BOTSEP:                /* at bottom; look for middle */
  1193.     while (linetbl[--ln].type != MIDSEP)
  1194.       ;
  1195.     break;
  1196.   }
  1197.  
  1198.   /* find out what number diff this is */
  1199.   for (i=0, line=linetbl, diff=0; i <= ln; i++, line++) {
  1200.     if (line->type == MIDSEP) diff++;
  1201.   }
  1202.   set_curr_diff(ln, diff);
  1203. }
  1204.  
  1205.  
  1206. static void
  1207. collapse_diff(void)
  1208. {
  1209.   int i, n;
  1210.  
  1211.   if (linetbl[diffbeg].incl) {
  1212.     LINE tmp;
  1213.     if (diffmid == diffend-1) return;    /* nothing to move */
  1214.     /* The lower diff is moved up beneath the upper diff:
  1215.           A      A
  1216.           B      B
  1217.           C  ->  C
  1218.           -      D
  1219.           D      E
  1220.           E      -
  1221.     */
  1222.     tmp = linetbl[diffmid];
  1223.     for (i=diffmid, diffmid=diffend-1; i <= diffmid; i++) {
  1224.       linetbl[i] = i==diffmid ? tmp : linetbl[i+1];
  1225.       linetbl[i].incl = TRUE;
  1226.       putline(i);
  1227.     }
  1228.   } else {
  1229.     LINE *tmp;
  1230.     if (diffmid == diffbeg+1) return;    /* nothing to move */
  1231.     /* The upper diff is moved beneath the lower diff:
  1232.           A      -
  1233.           B      D
  1234.           C  ->  E
  1235.           -      A    \
  1236.           D      B     > these (-DE) are remembered in tmp
  1237.           E      C    /
  1238.     */
  1239.     n = diffend-diffmid;
  1240.     tmp = malloc(n * sizeof(*tmp));
  1241.     memcpy(tmp, linetbl+diffmid, n * sizeof(*tmp));
  1242.     for (i = diffmid-1; i > diffbeg; i--) {
  1243.       linetbl[i+n] = linetbl[i];
  1244.       linetbl[i+n].incl = TRUE;
  1245.       putline(i+n);
  1246.     }
  1247.     diffmid = diffbeg+1;
  1248.     for (i = 0; i < n; i++) {
  1249.       linetbl[diffmid+i] = tmp[i];
  1250.       putline(diffmid+i);
  1251.     }
  1252.     free(tmp);
  1253.   }
  1254.   modified = TRUE;
  1255.   goto_curr_diff(FALSE);
  1256.   (void)refresh();
  1257. }
  1258.  
  1259. static void
  1260. choose_diff(BOOLEAN diff1)
  1261. {
  1262.   int ln;
  1263.  
  1264.   if ( diff1 &&  linetbl[diffbeg].incl ||
  1265.       !diff1 && !linetbl[diffbeg].incl) return;    /* no change in diff */
  1266.   if (diff1) sel1++;                    /* change from file1 to 2 */
  1267.   else       sel1--;                    /* vice versa */
  1268.  
  1269.   for (ln = diffbeg; ln < diffmid; ln++) {
  1270.     linetbl[ln].incl = diff1;
  1271.     putline(ln);
  1272.   }
  1273.   for (ln = diffend; diffmid < ln; ln--) {
  1274.     linetbl[ln].incl = !diff1;
  1275.     putline(ln);
  1276.   }
  1277.   (void)refresh();
  1278. }
  1279.  
  1280.  
  1281. static void
  1282. choose_all_diff(BOOLEAN diff1)
  1283. {
  1284.   BOOLEAN incl;
  1285.   LINE *line;
  1286.   int ln;
  1287.  
  1288.   incl = diff1;
  1289.   for (ln=0, line=linetbl; ln < nline; ln++, line++) {
  1290.     if (line->type == COMMON) continue;
  1291.     line->incl = incl;
  1292.     if (line->type == MIDSEP || line->type == BOTSEP) {
  1293.       incl = !incl;
  1294.     }
  1295.     putline(ln);            /* non-visible lines are ignored */
  1296.   }
  1297.   sel1 = diff1 ? ndiff : 0;
  1298.   (void)refresh();
  1299. }
  1300.  
  1301.  
  1302. static char *
  1303. read_num(char *str, int *val)
  1304. {
  1305.   int v;
  1306.  
  1307.   for (v = 0; isdigit(*str); str++) {
  1308.     v = v*10 + *str -'0';
  1309.   }
  1310.   *val = v;
  1311.   return str;
  1312. }
  1313.  
  1314.  
  1315. static BOOLEAN
  1316. read_edit_cmd(FILE *fp, EDITCMD *cmd, BOOLEAN *errmsg)
  1317. {
  1318.   char buf[150], *p;
  1319.   int skip;
  1320.  
  1321.   do {
  1322.     if (fgets(buf, sizeof(buf), fp) == NULL) return FALSE;
  1323.     p = buf;
  1324.     p = read_num(p, &cmd->beg1);
  1325.     if (*p==',') p = read_num(p+1, &cmd->end1);
  1326.     else         cmd->end1 = cmd->beg1;
  1327.     cmd->cmd = *p++;
  1328.     p = read_num(p, &cmd->beg2);
  1329.     if (*p==',') p = read_num(p+1, &cmd->end2);
  1330.     else         cmd->end2 = cmd->beg2;
  1331.  
  1332.     switch (cmd->cmd) {
  1333.     case 'a': skip = cmd->end2-cmd->beg2+1; break;
  1334.     case 'c': skip =   (cmd->end1-cmd->beg1+1)
  1335.              + (cmd->end2-cmd->beg2+1) + 1; break;
  1336.     case 'd': skip = cmd->end1-cmd->beg1+1; break;
  1337.     default:
  1338.       if (buf[0]) {
  1339.     buf[sizeof(buf)-1] = '\0';      /* make sure it's 0-terminated */
  1340.     for (p = buf; *p; p++) {
  1341.       if (!isprint(*(unsigned char*)p)) break;
  1342.     }
  1343.     *p = '\0';
  1344.     message("diff says: \"%s\"", buf);
  1345.       } else {
  1346.     message("diff produced no output");
  1347.     return FALSE;            /* probably bogus diff-file */
  1348.       }
  1349.       *errmsg = TRUE;
  1350.       skip = -1;
  1351.     }
  1352.   } while (skip < 0);
  1353.   skip_lines(fp, skip);
  1354.   return TRUE;
  1355. }
  1356.  
  1357.  
  1358. static void
  1359. set_tab_width(void)
  1360. {
  1361.   char buf[200];
  1362.   int val;
  1363.   int ln;
  1364.  
  1365.   buf[0] = '\0';
  1366.   for (;;) {
  1367.     if (!input(INPUT_NUMERIC, buf, "set tabwidth (now %d):", tabwidth)) return;
  1368.     val = atoi(buf);
  1369.     if (val > 0 && val <= MAXTABWIDTH) break;
  1370.     message("tabwidth must be between 1 and %d", MAXTABWIDTH);
  1371.   }
  1372.   tabwidth = val;
  1373.  
  1374.   for (ln = topln; ln < topln+VIEWLINES; ln++) {
  1375.     putline(ln);
  1376.   }
  1377.   (void)refresh();
  1378. }
  1379.  
  1380.  
  1381. static void
  1382. set_focus()
  1383. {
  1384.   int ln;
  1385.  
  1386.   switch (Focus) {
  1387.   case FOCUS_BOTH:  Focus = FOCUS_FILE1; break;
  1388.   case FOCUS_FILE1: Focus = FOCUS_FILE2; break;
  1389.   case FOCUS_FILE2: Focus = FOCUS_BOTH;  break;
  1390.   }
  1391.   for (ln = topln; ln < topln+VIEWLINES; ln++) {
  1392.     putline(ln);
  1393.   }
  1394.   (void)refresh();
  1395. }
  1396.  
  1397.  
  1398. static BOOLEAN
  1399. write_file(char *fname, char *eol, int flags)
  1400. {
  1401.   char tmpfname[FILENAME_MAX+20], *p;
  1402.   struct stat st;
  1403.   FILE *fp;
  1404.   int ln;
  1405.   char *text;
  1406.   int i, len, eol_len;
  1407.   BOOLEAN error;
  1408.  
  1409.   if (flags & EXISTING_FILE) {
  1410.     /*  Write viffed output to a temp file and rename() that if the
  1411.      * write succeeds.  We can't use tmpnam() because files can't be
  1412.      * rename'd across mounted file systems and the TMP dir might be
  1413.      * on another disk.
  1414.      *  The stat()+fopen() below really should be an atomic operation,
  1415.      * but the risk of another viff testing and choosing exactly the
  1416.      * same name at the same time is rather theoretical.
  1417.      */
  1418.     strcpy(tmpfname, fname);
  1419.     p = strip_path(tmpfname);
  1420.     for (fp=NULL, i=0; i < 20; i++) {    /* try max n temp names */
  1421.       sprintf(p, "viff%d.tmp", i);
  1422.       if (stat(tmpfname, &st) == 0) continue;  /* name exists; try another */
  1423.       if ((fp=fopen(tmpfname, "wb")) != NULL) break;
  1424.     }
  1425.     if (stat(fname, &st) != 0) st.st_mode = 0;
  1426.   } else {
  1427.     fp = fopen(fname, "wb");
  1428.   }
  1429.   if (fp == NULL) {
  1430. #ifdef UNIX
  1431.     if (access(".", W_OK) != 0) {
  1432.       char buf[1024], *cwd = getcwd(buf, sizeof(buf));
  1433.       if (!cwd) cwd = ".";
  1434.       message("cannot write to directory \"%s\"", cwd);
  1435.     } else
  1436. #endif
  1437.       message("cannot write to \"%s\": %s", fname, my_strerror());
  1438.     return FALSE;
  1439.   }
  1440.   statusline(0, "saving file %s ...", fname); refresh();
  1441.   clearerr(fp); errno = 0;
  1442.   if (flags & WRITE_ALL) {
  1443.     ffile_info(fp);
  1444.   }
  1445.   eol_len = strlen(eol);
  1446.   for (ln = 0; ln < nline; ln++) {
  1447.     if (flags & WRITE_ALL) {
  1448.       switch (linetbl[ln].type) {
  1449.       case COMMON: /* NOBREAK */
  1450.       case DIFF:   text = linetbl[ln].txt; len = linetbl[ln].len; break;
  1451.       case TOPSEP: text = toptext; len = sizeof(toptext)-1; break;
  1452.       case MIDSEP: text = midtext; len = sizeof(midtext)-1; break;
  1453.       case BOTSEP: text = bottext; len = sizeof(bottext)-1; break;
  1454.       }
  1455.     } else if (linetbl[ln].incl && !SYSLINE(linetbl+ln)) {
  1456.       text = linetbl[ln].txt; len = linetbl[ln].len;
  1457.     } else continue;                  /* unselected or system line */
  1458.     fwrite(text, 1, len, fp);
  1459.     fwrite(eol, 1, eol_len, fp);
  1460.   }
  1461.   error = ferror(fp);
  1462.   fclose(fp);
  1463.   if (error) {
  1464.     message("error writing to \"%s\": %s", fname, my_strerror());
  1465.     if ((flags & EXISTING_FILE) && remove(tmpfname)) {
  1466.       message("could not remove temporary file \"%s\": %s",
  1467.           tmpfname, my_strerror());
  1468.     }
  1469.     return FALSE;
  1470.   }
  1471.   if (flags & EXISTING_FILE) {
  1472.     if (remove(fname)) {        /* could not remove the old file */
  1473.       struct stat st;
  1474.       if (stat(fname, &st) == 0) {    /* file exist, but remove failed */
  1475.     message("cannot remove existing file \"%s\": %s",
  1476.         fname, my_strerror());
  1477.     if (remove(tmpfname)) {        /* aik! can't remove our tmpfile! */
  1478.       message("could not remove temporary file \"%s\": %s",
  1479.           tmpfname, my_strerror());
  1480.     }
  1481.     return FALSE;
  1482.       }
  1483.     }
  1484.     if (rename(tmpfname, fname)) {    /* could not rename to real name */
  1485.       message("rename failed: %s; file saved as \"%s\"!",
  1486.           my_strerror(), tmpfname);
  1487.       return FALSE;
  1488.     }
  1489. #ifdef UNIX
  1490.     if (st.st_mode != 0) {
  1491.       /* don't bother checking for success; this is a favor, not mandatory */
  1492.       chown(fname, st.st_uid, st.st_gid);
  1493.       chmod(fname, st.st_mode);
  1494.     }
  1495. #endif
  1496.   }
  1497.   viff_statusline();
  1498.   return TRUE;
  1499. }
  1500.  
  1501.  
  1502. static int
  1503. user_commands(void)
  1504. {
  1505.   int ch;
  1506.  
  1507.   for (;;) {
  1508.     viff_statusline();
  1509.     (void)refresh();
  1510.     ch = getch();
  1511.     switch (ch) {
  1512.     case KEY_IC:
  1513.     case KEYCTRL('O'):
  1514.       edit_mode();
  1515.       break;
  1516.     case KEY_HOME:
  1517.     case KEYCTRL('A'):
  1518. #ifdef KEY_A1
  1519.     case KEY_A1:
  1520. #endif
  1521.       goto_first_diff();
  1522.       break;
  1523.     case KEY_END:
  1524.     case KEYCTRL('E'):
  1525.       goto_last_diff();
  1526.       break;
  1527.     case KEY_UP:
  1528.     case KEYCTRL('P'):
  1529. #ifdef KEY_A2
  1530.     case KEY_A2:
  1531. #endif
  1532.       goto_prev_diff();
  1533.       break;
  1534.     case KEY_DOWN:
  1535.     case KEYCTRL('N'):
  1536. #ifdef KEY_C2
  1537.     case KEY_C2:
  1538. #endif
  1539.       goto_next_diff();
  1540.       break;
  1541.     case KEY_LEFT:
  1542.     case KEYCTRL('B'):
  1543. #ifdef KEY_B1
  1544.     case KEY_B1:
  1545. #endif
  1546.       choose_diff(TRUE);
  1547.       break;
  1548.     case KEY_RIGHT:
  1549.     case KEYCTRL('F'):
  1550. #ifdef KEY_B3
  1551.     case KEY_B3:
  1552. #endif
  1553.       choose_diff(FALSE);
  1554.       break;
  1555.     case '.':
  1556. #ifdef KEY_B2
  1557.     case KEY_B2:
  1558. #endif
  1559.       goto_curr_diff(TRUE);
  1560.       break;
  1561.     case ' ':
  1562.       choose_diff(!linetbl[diffbeg].incl);
  1563.       break;
  1564.     case '(':
  1565.       choose_all_diff(TRUE);
  1566.       break;
  1567.     case ')':
  1568.       choose_all_diff(FALSE);
  1569.       break;
  1570.     case KEY_PPAGE:
  1571. #ifdef ALT_V
  1572.     case ALT_V:
  1573. #endif
  1574. #ifdef KEY_A3
  1575.     case KEY_A3:
  1576. #endif
  1577.       goto_prev_page();
  1578.       break;
  1579.     case KEY_NPAGE:
  1580.     case KEYCTRL('V'):
  1581. #ifdef KEY_C3
  1582.     case KEY_C3:
  1583. #endif
  1584.       goto_next_page();
  1585.       break;
  1586.     case KEYCTRL('L'):
  1587.       goto_nearest_diff(topln+VIEWLINES/2);
  1588.       break;
  1589. #ifdef KEY_SHELP
  1590.     case KEY_SHELP:
  1591. #endif
  1592. #ifdef KEY_LHELP
  1593.     case KEY_LHELP:
  1594. #endif
  1595.     case 'h':
  1596.     case 'H':
  1597.     case KEY_F(1):
  1598.       help(viffmode_help, ARRAYSIZE(viffmode_help));
  1599.       break;
  1600.     case KEYCTRL('S'):
  1601.     case KEYCTRL('R'):
  1602.       search(ch);
  1603.       break;
  1604. #ifdef UNIX
  1605.     case 'r':
  1606.     case 'R':
  1607.     case KEY_REFRESH:
  1608.       (void)wrefresh(curscr);
  1609.       break;
  1610. #endif
  1611.     case '\t':
  1612.       set_focus();
  1613.       break;
  1614.     case 't':
  1615.     case 'T':
  1616.       set_tab_width();
  1617.       break;
  1618.     case 'i':
  1619.     case 'I':
  1620.       file_info();
  1621.       break;
  1622.     case '+':
  1623.       collapse_diff();
  1624.       break;
  1625.     case '1':
  1626.       if (write_file(Fname1, Eol1, EXISTING_FILE)) return ch;
  1627.       break;
  1628.     case '2':
  1629.       if (write_file(Fname2, Eol2, EXISTING_FILE)) return ch;
  1630.       break;
  1631.     case '3':
  1632.       if (write_file(Fname1, Eol1, EXISTING_FILE) &&
  1633.       write_file(Fname2, Eol2, EXISTING_FILE)) return ch;
  1634.       break;
  1635.     case '4':
  1636.     case '5':
  1637.       { char buf[300];
  1638.     if (!input_filename(buf, ch)) break;
  1639.     if (write_file(buf, EOL, ch=='5'?WRITE_ALL:0) && ch=='4') {
  1640.       modified = FALSE;
  1641.     }
  1642.       }
  1643.       break;
  1644.     case '<':
  1645.     case '>':
  1646.     case 'q':
  1647.     case 'Q':
  1648.     case KEY_ESCAPE:
  1649.       if (!confirm_quit()) break;
  1650.       if (ch == 'Q') exit(EXIT_SUCCESS);
  1651.       return ch;
  1652.     }
  1653.   }
  1654. }
  1655.  
  1656.  
  1657. static int
  1658. first_diff_to_show(void)
  1659. {
  1660.   int i, ln;
  1661.  
  1662.   if (ndiff == 0) return 0;
  1663.   if (!ignoreheaders) return 1;
  1664.  
  1665.   for (ln = 0; ln < nline; ln++) {
  1666.     if (linetbl[ln].type == TOPSEP) break;
  1667.   }
  1668.   if (ln+4 < nline &&
  1669.       linetbl[ln+1].type == DIFF &&
  1670.       linetbl[ln+2].type == MIDSEP &&
  1671.       linetbl[ln+3].type == DIFF &&
  1672.       linetbl[ln+4].type == BOTSEP) {
  1673.     for (i = 0; header_table[i]; i++) {
  1674.       if (is_header_line(linetbl+ln+1, header_table[i]) &&
  1675.       is_header_line(linetbl+ln+3, header_table[i])) {
  1676.     return ndiff==1 ? 0 : 2;
  1677.       }
  1678.     }
  1679.   }
  1680.   return 1;
  1681. }
  1682.  
  1683.  
  1684. static BOOLEAN
  1685. is_header_line(LINE *line, char *header)
  1686. {
  1687.   int headerlen = strlen(header);
  1688.   int maxpos = line->len - headerlen;
  1689.   int i;
  1690.  
  1691.   for (i = 0; i < maxpos; i++) {
  1692.     if (strncmp(line->txt+i, header, headerlen) == 0) return TRUE;
  1693.   }
  1694.   return FALSE;
  1695. }
  1696.  
  1697.  
  1698. static FILE *
  1699. open_viff_file(char *fname, char **eol, int *chp)
  1700. {
  1701.   FILE *fp;
  1702.  
  1703.   /* Recognize DOS or Unix end-of-line:
  1704.    *   DOS:  CR+LF  (\x0d\x0a)
  1705.    *   Unix: LF     (\x0a)
  1706.    */
  1707.   *eol = EOL;
  1708.   if ((fp=fopen(fname, "rb")) != NULL) {
  1709.     int ch;
  1710.     while ((ch=getc(fp)) != EOF) {
  1711.       if (ch == '\x0d') {
  1712.     if (getc(fp) == '\x0a') {    /* DOS */
  1713.       *eol = DOS_EOL;
  1714.       break;
  1715.     }
  1716.       } else if (ch == '\x0a') {    /* Unix */
  1717.     *eol = UNIX_EOL;
  1718.     break;
  1719.       }
  1720.     }
  1721.     fclose(fp);
  1722.   }
  1723.  
  1724.   if ((fp=fopen(fname, "r")) == NULL && verbose) {
  1725.     *chp = message("could not open %s: %s", fname, my_strerror());
  1726.   }
  1727.   return fp;
  1728. }
  1729.  
  1730.  
  1731. static BOOLEAN
  1732. viff_files(int *chp)
  1733. {
  1734.   char buf[MAXLINELEN], *p;
  1735.   FILE *fpdiff;
  1736.   EDITCMD cmd;
  1737.   FILE *fp1, *fp2;
  1738.   int line1, line2;
  1739.   int i, skip, firstdiff;
  1740.   BOOLEAN errmsg;
  1741.  
  1742.   if ((fp1=open_viff_file(Fname1, &Eol1, chp)) == NULL) return FALSE;
  1743.   if ((fp2=open_viff_file(Fname2, &Eol2, chp)) == NULL) {
  1744.     fclose(fp1);
  1745.     return FALSE;
  1746.   }
  1747.  
  1748.   /* Check that the files are in fact not the same file (eg through a link)
  1749.    */
  1750. #ifdef UNIX /* Under DOS, st_ino might not have a value (ie always be 0) */
  1751.   { struct stat stat1, stat2;
  1752.     if (stat(Fname1, &stat1)==0 && stat(Fname2, &stat2)==0 &&
  1753.     stat1.st_dev==stat2.st_dev && stat1.st_ino==stat2.st_ino) {
  1754.       *chp = message("%s and %s is the same file", Fname1, Fname2);
  1755.       fclose(fp1);
  1756.       fclose(fp2);
  1757.       return FALSE;
  1758.     }
  1759.   }
  1760. #endif
  1761.  
  1762.   if (!report) {
  1763.     statusline(0, "diffing %s and %s ...", Fname1, Fname2); refresh();
  1764.   }
  1765.  
  1766.   sprintf(buf, "%s %s %s %s", diffcmd, options, Fname1, Fname2);
  1767.   fpdiff = popen(buf, "r");
  1768.   if (fpdiff == NULL) {
  1769.     fclose(fp1); fclose(fp2);
  1770.     *chp = message("error running diff: %s", my_strerror());
  1771.     return FALSE;
  1772.   }
  1773.  
  1774. #if 0
  1775.   /* I'm taking a chance here. Traditionally 0 means no error and not-0
  1776.    * means error. However, it seems that some (most?) diff programs return
  1777.    * 0 for "no differences" and 1(!) for "differences". Therefore I choose
  1778.    * only to interpret error codes above 1 as real errors.
  1779.    */
  1780.   if (errcode > 1) {
  1781.     char buf[200];
  1782.     *chp = message("error viffing %s and %s\n", Fname1, Fname2);
  1783.     while (fgets(buf, sizeof buf, fpdiff)) {
  1784.       *chp = message("diff complains: \"%s\"", buf);
  1785.     }
  1786.     return FALSE;
  1787.   }
  1788. #endif
  1789.  
  1790.   if (!report) {
  1791.     statusline(0, "viffing %s and %s ...", Fname1, Fname2); refresh();
  1792.   }
  1793.  
  1794.   line1 = line2 = 1;
  1795.   diffbeg = diffmid = diffend = -1;
  1796.   Focus = FOCUS_BOTH;
  1797.  
  1798.   errmsg = FALSE;
  1799.   for (ndiff = sel1 = 0; read_edit_cmd(fpdiff, &cmd, &errmsg); ndiff++, sel1++) {
  1800.     skip = cmd.beg1 - line1;            /* the next skip lines are identical */
  1801.     if (cmd.cmd == 'a') skip++;
  1802.  
  1803.     for (i = 0; i < skip; i++, line1++, line2++) {  /* read them from file1 */
  1804.       fgets(buf, sizeof(buf), fp1);
  1805.       buffer_text_line(COMMON, TRUE, buf, line1, line2);
  1806.     }
  1807.     skip_lines(fp2, skip);              /* these are the same; just skip it */
  1808.  
  1809.     if (diffbeg == -1) diffbeg = nline;
  1810.     (void)buffer_line(TOPSEP, TRUE);
  1811.     if (cmd.cmd=='c' || cmd.cmd=='d') { /* something new in file1 */
  1812.       for (; line1 <= cmd.end1; line1++) {
  1813.     fgets(buf, sizeof(buf), fp1);
  1814.     buffer_text_line(DIFF, TRUE, buf, line1, 0);
  1815.       }
  1816.     }
  1817.     if (diffmid == -1) diffmid = nline;
  1818.     (void)buffer_line(MIDSEP, TRUE);
  1819.     if (cmd.cmd=='c' || cmd.cmd=='a') { /* something new in file2 */
  1820.       for (; line2 <= cmd.end2; line2++) {
  1821.     fgets(buf, sizeof(buf), fp2);
  1822.     buffer_text_line(DIFF, FALSE, buf, 0, line2);
  1823.       }
  1824.     }
  1825.     if (diffend == -1) diffend = nline;
  1826.     (void)buffer_line(BOTSEP, FALSE);
  1827.   }
  1828.   if (0 < ndiff) {            /* remaining lines are identical */
  1829.     while (fgets(buf, sizeof(buf), fp1) != NULL) {
  1830.       buffer_text_line(COMMON, TRUE, buf, line1++, line2++);
  1831.     }
  1832.   }
  1833.   fclose(fp1);
  1834.   fclose(fp2);
  1835.   pclose(fpdiff);
  1836.  
  1837.   if (ndiff == 0 && errmsg)
  1838.     return FALSE;
  1839.  
  1840.   if (report) {                /* reports are done now */
  1841.     if (brief_report) {
  1842.       if (ndiff > 0) printf("%s\n", Fname1);
  1843.     } else if (verbose || ndiff > 0) {
  1844.       printf("%3d diff%s between %s and %s\n",
  1845.          ndiff, ndiff==1?" ":"s", Fname1, Fname2);
  1846.       Ndiff += ndiff;
  1847.       Ndiffrep++;
  1848.     }
  1849.     return TRUE; /* don't-care; reports don't "go back" */
  1850.   }
  1851.  
  1852.   firstdiff = first_diff_to_show();
  1853.   if (firstdiff == 0) {
  1854.     free_difflines();
  1855.     if (verbose) {
  1856.       if (ndiff == 0) {
  1857.     *chp = message("files %s and %s are identical", Fname1, Fname2);
  1858.       } else {
  1859.     *chp = message("files %s and %s have a header difference",
  1860.                Fname1, Fname2);
  1861.       }
  1862.     }
  1863.     return FALSE;
  1864.   }
  1865.  
  1866.   if (strcmp(p=strip_path(Fname1), strip_path(Fname2)) == 0) {
  1867.     strncpy(viffname,  p, VIFFNAME_LEN)[VIFFNAME_LEN-1] = '\0';
  1868.   } else {
  1869.     viffname[0] = '\0';
  1870.   }
  1871.   build_frame_texts();
  1872.   (void)clear();
  1873.   topln = -VIEWLINES;                   /* force a redraw instead of scroll */
  1874.   goto_diff(firstdiff);
  1875.   *chp = user_commands();
  1876.   modified = FALSE;
  1877.   sel1 = ndiff = 0;
  1878.   free_difflines();
  1879.   return TRUE;
  1880. }
  1881.  
  1882.  
  1883. static void
  1884. check_for_stop(void)
  1885. {
  1886.   int ch;
  1887.  
  1888.   if (report) return;
  1889.   if (nodelay(stdscr, TRUE) == ERR) return;
  1890.   ch = getch();
  1891.   (void)nodelay(stdscr, FALSE);
  1892.   if (ch == 'Q' ||
  1893.       (ch==KEY_ESCAPE || ch=='q') && ask(stop_help, ARRAYSIZE(stop_help),
  1894.                      "yn", 'n', "quit viff?")=='y') {
  1895.     exit(EXIT_SUCCESS);
  1896.   }
  1897. }
  1898.  
  1899.  
  1900. int
  1901. main(int argc, char *argv[])
  1902. {
  1903.   char path[FILENAME_MAX], *pathend;
  1904.   char *diff1, *diffN;
  1905.   char *skipname = NULL;
  1906.   BOOLEAN skipgenerated = FALSE;
  1907.   BOOLEAN isdir1, isdirN;
  1908.   int ch, i;
  1909.  
  1910.   if (argc == 2 && strcmp(argv[1], "vaff") == 0) {  /* #include <stdjoke.h> */
  1911.     printf("vuff!\n");
  1912.     exit(EXIT_SUCCESS);
  1913.   }
  1914.   signal(SIGINT, SIG_IGN);
  1915.  
  1916.   options = xstrdup("");
  1917.   atexit(cleanup);
  1918.   while (--argc > 0 && (++argv)[0][0] == '-') {
  1919.     char *op = argv[0];
  1920.     if (op[1] == '\0') error("missing option letter");
  1921.     while (*++op) {
  1922.       switch (*op) {
  1923.       case 't':
  1924.     if (op[1] || --argc == 0) {
  1925.       error("missing tabsize argument for -t");
  1926.     }
  1927.     tabwidth = atoi(*++argv);
  1928.     if (tabwidth < 1 || tabwidth > MAXTABWIDTH) {
  1929.       error("tabwidth must be between 1 and %d", MAXTABWIDTH);
  1930.     }
  1931.     break;
  1932.       case 'o':
  1933.     if (op[1] || --argc == 0) {
  1934.       error("missing option argument for -o");
  1935.     } else {
  1936.       int len = strlen(options);
  1937.       ++argv;
  1938.       options = (char*)xrealloc(options, len+1+strlen(*argv)+1);
  1939.       sprintf(options+len, " %s", *argv);
  1940.     }
  1941.     break;
  1942.       case 'f':
  1943.     if (op[1] || --argc == 0) {
  1944.       error("missing filename argument for -f");
  1945.     }
  1946.     skipname = lower_str(*++argv);
  1947.     break;
  1948.       case 'p':
  1949.     if (op[1] || --argc == 0) {
  1950.       error("missing program argument for -p");
  1951.     }
  1952.     diffcmd = xstrdup(lower_str(*++argv));
  1953.     break;
  1954.       case 'h':
  1955.       case 'H':
  1956.     version_info();
  1957.     printf("\n");
  1958.     extended_usage();
  1959.     break;
  1960.       case 'v': verbose       = TRUE; break;
  1961.       case 'L': brief_report  = TRUE; /*NOBREAK*/
  1962.       case 'l': report        = TRUE; break;
  1963.       case 'i': ignoreheaders = TRUE; break;
  1964.       case 'x': skipgenerated = TRUE; break;
  1965.       case 'm': monochrome    = TRUE; break;
  1966.       case 'V':
  1967.     version_info();
  1968.     return 0;
  1969.       default:
  1970.     error("unknown option '%.20s'\nuse viff -h for help", argv[0]);
  1971.       }
  1972.     }
  1973.   }
  1974.   if (argc < 2) brief_usage();
  1975.  
  1976.   if (!diffcmd) diffcmd = xstrdup(DIFFCMD);
  1977.   if (!find_diffcmd()) {
  1978.     error("could not find %s", diffcmd);
  1979.   }
  1980.  
  1981.   diff1 = argv[0];
  1982.   isdir1 = is_dir(diff1);
  1983.   diffN = argv[argc-1];
  1984.   isdirN = is_dir(diffN);
  1985.   if (isdir1 && isdirN) {
  1986.     if (argc > 2) error("both first and last argument is a directory");
  1987.     construct_argv(diff1, diffN, &argc, &argv);
  1988.     if (argc < 2) error("there are no files in %s", diff1);
  1989.     diff1 = argv[0];
  1990.     isdir1 = FALSE;
  1991.   } else if (argc > 2 && !isdir1 && !isdirN) {
  1992.     error("please specify a directory to viff these %d files against", argc);
  1993.   }
  1994.  
  1995.   init_curses();
  1996.   init_edit_mode();
  1997.  
  1998.   for (i = 0; i < argc; i++) {        /* make sure DOS arguments are lower case */
  1999.     lower_str(argv[i]);
  2000.   }
  2001.  
  2002.   /* If there are only two single arguments and one is a directory,
  2003.      make viff behave as if two files had been specified, so it
  2004.      will be verbose and not skip binaries, generated etc.
  2005.    */
  2006.   if (argc == 2) {
  2007.     verbose = TRUE;
  2008.     if (isdir1 && !isdirN) {
  2009.       strcpy(make_path(path, diff1), strip_path(diffN));
  2010.       diff1 = path;
  2011.       isdir1 = FALSE;
  2012.     } else if (!isdir1 && isdirN) {
  2013.       strcpy(make_path(path, diffN), strip_path(diff1));
  2014.       diffN = path;
  2015.       isdirN = FALSE;
  2016.     }
  2017.   }
  2018.  
  2019.   if (!isdir1 && !isdirN) {
  2020.     Fileno = Nfiles = 1;
  2021.     Fname1 = diff1; Fname2 = diffN;
  2022.     while (viff_files(&ch) && ch == '<')
  2023.       ;
  2024.   } else {
  2025.     int firstfile=1;
  2026.     pathend = make_path(path, isdir1 ? diff1 : diffN);
  2027.     Nfiles = argc-1;
  2028.     if (isdirN) argv--;
  2029.     skip_directories(firstfile, argc, argv);
  2030.     skip_binary_files(firstfile, argc, argv);
  2031.     if (skipgenerated) {
  2032.       skip_generated_files(firstfile, argc, argv);
  2033.     }
  2034.     Fileno = firstfile;
  2035.     if (skipname) {
  2036.       BOOLEAN skipwithpath = strpbrk(skipname, "\\:/")!=NULL;
  2037.       for (; Fileno < argc; Fileno++) {
  2038.     strcpy(pathend, strip_path(argv[Fileno]));
  2039.     if (skipwithpath) {        /* a name with path must match */
  2040.       if (strcmp(skipname, path) == 0 ||
  2041.           strcmp(skipname, argv[Fileno]) == 0) break;
  2042.     } else {            /* a name without path must match */
  2043.       if (strcmp(skipname, pathend) == 0) break;
  2044.     }
  2045.       }
  2046.     }
  2047.     while (Fileno < argc) {
  2048.       check_for_stop();
  2049.       if (Fileno < firstfile) {
  2050.     ch = '\0'; Fileno++;
  2051.     continue;
  2052.       }
  2053.       if (argv[Fileno]) {
  2054.     strcpy(pathend, strip_path(argv[Fileno]));
  2055.     Fname1 = isdir1 ? path : argv[Fileno];
  2056.     Fname2 = isdir1 ? argv[Fileno] : path;
  2057.     if (!viff_files(&ch)) {
  2058.       argv[Fileno] = NULL;
  2059.     }
  2060.       }
  2061.       if (ch=='<') Fileno--;
  2062.       else         Fileno++;
  2063.     }
  2064.  
  2065.     /* Only print summary for non-brief reports, and then only
  2066.        if verbose or there was any diffs at all.
  2067.      */
  2068.     if (report && !brief_report && (verbose || Ndiff > 0)) {
  2069.       printf("---------\n%3d diff%s in %d file%s\n",
  2070.          Ndiff, Ndiff==1?"":"s", Ndiffrep, Ndiffrep==1?"":"s");
  2071.     }
  2072.   }
  2073.  
  2074.   return 0;
  2075. }
  2076.